{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# RunnablePassthrough\n",
    "\n",
    "- Author: [Suhyun Lee](https://github.com/suhyun0115)\n",
    "- Peer Review:\n",
    "- Proofread : [Yun Eun](https://github.com/yuneun92)\n",
    "- This is a part of [LangChain Open Tutorial](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial)\n",
    "\n",
    "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/13-LangChain-Expression-Language/01-RunnablePassThrough.ipynb)[![Open in GitHub](https://img.shields.io/badge/Open%20in%20GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/13-LangChain-Expression-Language/01-RunnablePassThrough.ipynb)\n",
    "## Overview\n",
    "\n",
    "```RunnablePassthrough``` is a utility that facilitates unmodified data flow through a pipeline. Its ```invoke()``` method returns input data in its original form without alterations.\n",
    "\n",
    "This functionality allows seamless data transmission between pipeline stages.\n",
    "\n",
    "It frequently works in tandem with ```RunnableParallel``` for concurrent task execution, enabling the addition of new key-value pairs to the data stream.\n",
    "\n",
    "Common use cases for ```RunnablePassthrough``` include:\n",
    "\n",
    "- Direct data forwarding without transformation\n",
    "- Pipeline stage bypassing\n",
    "- Pipeline flow validation during debugging\n",
    "\n",
    "### Table of Contents\n",
    "\n",
    "- [Overview](#overview)\n",
    "- [Environment Setup](#environment-setup)\n",
    "- [Passing Data with RunnablePassthrough and RunnableParallel](#passing-data-with-runnablepassthrough-and-runnableparallel)\n",
    "  - [Example of Using RunnableParallel and RunnablePassthrough](#example-of-using-runnableparallel-and-runnablepassthrough)\n",
    "  - [Summary of Results](#summary-of-results)\n",
    "- [Search Engine Integration](#search-engine-integration)\n",
    "  - [Using RunnablePassthrough in a FAISS-Based RAG Pipeline](#using-runnablepassthrough-in-a-faiss-based-rag-pipeline)\n",
    "  - [Using Ollama](#using-ollama)\n",
    "    - [Ollama Installation Guide on Colab](#ollama-installation-guide-on-colab)\n",
    "\n",
    "### References\n",
    "\n",
    "- [LangChain Python API Reference > RunnablePassthrough](https://python.langchain.com/api_reference/core/runnables/langchain_core.runnables.passthrough.RunnablePassthrough.html#runnablepassthrough)\n",
    "- [Ollama official website](https://ollama.com/)\n",
    "- [GitHub tutorial](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/04-Model/10-Ollama.ipynb)\n",
    "\n",
    "----"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Environment Setup\n",
    "\n",
    "Set up the environment. You may refer to [Environment Setup](https://wikidocs.net/257836) for more details.\n",
    "\n",
    "**[Note]**\n",
    "- ```langchain-opentutorial``` is a package that provides a set of easy-to-use environment setup, useful functions and utilities for tutorials. \n",
    "- You can checkout the [```langchain-opentutorial```](https://github.com/LangChain-OpenTutorial/langchain-opentutorial-pypi) for more details."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install langchain-opentutorial"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install required packages\n",
    "from langchain_opentutorial import package\n",
    "\n",
    "package.install(\n",
    "    [\n",
    "        \"langsmith\",\n",
    "        \"langchain_openai\",\n",
    "        \"langchain_core\",\n",
    "        \"langchain-ollama\",\n",
    "        \"langchain_community\",\n",
    "        \"faiss-cpu\",\n",
    "    ],\n",
    "    verbose=False,\n",
    "    upgrade=False,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you want to get automated tracing of your model calls you can also set your LangSmith API key by uncommenting below code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Set environment variables\n",
    "from langchain_opentutorial import set_env\n",
    "\n",
    "set_env(\n",
    "    {\n",
    "        \"OPENAI_API_KEY\": \"\",\n",
    "        \"LANGCHAIN_API_KEY\": \"\",\n",
    "        \"LANGCHAIN_TRACING_V2\": \"true\",\n",
    "        \"LANGCHAIN_ENDPOINT\": \"https://api.smith.langchain.com\",\n",
    "        \"LANGCHAIN_PROJECT\": \"LangChain-Expression-Language\",\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can alternatively set API keys such as ```OPENAI_API_KEY``` in a ```.env``` file and load them.\n",
    "\n",
    "[Note] This is not necessary if you've already set the required API keys in previous steps."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Load API keys from .env file\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "load_dotenv(override=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Passing Data with ```RunnablePassthrough``` and ```RunnableParallel```\n",
    "\n",
    "```RunnablePassthrough``` is a utility that **passes data through unchanged** or adds minimal information before forwarding.\n",
    "\n",
    "It commonly integrates with ```RunnableParallel``` to map data under new keys.\n",
    "\n",
    "- **Standalone Usage**\n",
    "  \n",
    "  When used independently, ```RunnablePassthrough()``` returns the input data unmodified.\n",
    "\n",
    "- **Usage with ```assign```**\n",
    "  \n",
    "  When implemented with ```assign``` as ```RunnablePassthrough.assign(...)```, it augments the input data with additional fields before forwarding.\n",
    "\n",
    "By leveraging ```RunnablePassthrough```, you can maintain data integrity through pipeline stages while selectively adding required information.\n",
    "\n",
    "Let me continue reviewing any additional content. I'm tracking all modifications to provide a comprehensive summary once the review is complete."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Example of Using ```RunnableParallel``` and ```RunnablePassthrough```\n",
    "\n",
    "While ```RunnablePassthrough``` is effective independently, it becomes more powerful when combined with ```RunnableParallel```.\n",
    "\n",
    "This section demonstrates how to configure and run **parallel tasks** using the ```RunnableParallel``` class. The following steps provide a beginner-friendly implementation guide.\n",
    "\n",
    "1. **Initialize ```RunnableParallel```**\n",
    "   \n",
    "   Create a ```RunnableParallel``` instance to manage concurrent task execution.\n",
    "\n",
    "2. **Configure ```passed``` Task**\n",
    "   \n",
    "   - Define a ```passed``` task utilizing ```RunnablePassthrough```\n",
    "   - This task **preserves input data without modification**\n",
    "\n",
    "3. **Set Up ```extra``` Task**\n",
    "   \n",
    "   - Implement an ```extra``` task using ```RunnablePassthrough.assign()```\n",
    "   - This task computes triple the \"num\" value and stores it with key ```mult```\n",
    "\n",
    "4. **Implement ```modified``` Task**\n",
    "   \n",
    "   - Create a ```modified``` task using a basic function\n",
    "   - This function increments the \"num\" value by 1\n",
    "\n",
    "5. **Task Execution**\n",
    "   \n",
    "   - Invoke all tasks using ```runnable.invoke()```\n",
    "   - Example: Input ```{\"num\": 1}``` triggers concurrent execution of all defined tasks\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'passed': {'num': 1}, 'extra': {'num': 1, 'mult': 3}, 'modified': 2}"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_core.runnables import RunnableParallel, RunnablePassthrough\n",
    "\n",
    "runnable = RunnableParallel(\n",
    "    # Sets up a Runnable that returns the input as-is.\n",
    "    passed=RunnablePassthrough(),\n",
    "    # Sets up a Runnable that multiplies the \"num\" value in the input by 3 and returns the result.\n",
    "    extra=RunnablePassthrough.assign(mult=lambda x: x[\"num\"] * 3),\n",
    "    # Sets up a Runnable that adds 1 to the \"num\" value in the input and returns the result.\n",
    "    modified=lambda x: {\"num\": x[\"num\"] + 1},\n",
    ")\n",
    "\n",
    "# Execute the Runnable with {\"num\": 1} as input.\n",
    "result = runnable.invoke({\"num\": 1})\n",
    "\n",
    "# Print the result.\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'num': 1, 'mult': 3}"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "r = RunnablePassthrough.assign(mult=lambda x: x[\"num\"] * 3)\n",
    "r.invoke({\"num\": 1})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Summary of Results\n",
    "\n",
    "When provided with input ```{\"num\": 1}```, each task produces the following output:\n",
    "\n",
    "1. **```passed```:** Returns unmodified input data\n",
    "   - Output: ```{\"num\": 1}```\n",
    "\n",
    "2. **```extra```:** Augments input with ```\"mult\"``` key containing triple the ```\"num\"``` value\n",
    "   - Output: ```{\"num\": 1, \"mult\": 3}```\n",
    "\n",
    "3. **```modified```:** Increments the ```\"num\"``` value by 1\n",
    "   - Output: ```{\"num\": 2}```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Search Engine Integration\n",
    "\n",
    "The following example illustrates an implementation of ```RunnablePassthrough```."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Using ```RunnablePassthrough``` in a FAISS-Based RAG Pipeline\n",
    "\n",
    "This code uses ```RunnablePassthrough``` in a FAISS-based RAG pipeline to pass retrieved context into a chat prompt.  \n",
    "It enables seamless integration of OpenAI embeddings for efficient retrieval and response generation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_community.vectorstores import FAISS\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_core.runnables import RunnablePassthrough\n",
    "from langchain_openai import ChatOpenAI, OpenAIEmbeddings\n",
    "\n",
    "# Create a FAISS vector store from text data.\n",
    "vectorstore = FAISS.from_texts(\n",
    "    [\n",
    "        \"Cats are geniuses at claiming boxes as their own.\",\n",
    "        \"Dogs have successfully trained humans to take them for walks.\",\n",
    "        \"Cats aren't fond of water, but the water in a human's cup is an exception.\",\n",
    "        \"Dogs follow cats around, eager to befriend them.\",\n",
    "        \"Cats consider laser pointers their arch-nemesis.\",\n",
    "    ],\n",
    "    embedding=OpenAIEmbeddings(),\n",
    ")\n",
    "\n",
    "# Use the vector store as a retriever.\n",
    "retriever = vectorstore.as_retriever()\n",
    "\n",
    "# Define a template.\n",
    "template = \"\"\"Answer the question based only on the following context:\n",
    "{context}\n",
    "\n",
    "Question: {question}\n",
    "\"\"\"\n",
    "\n",
    "# Create a chat prompt from the template.\n",
    "prompt = ChatPromptTemplate.from_template(template)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Initialize the ChatOpenAI model.\n",
    "model = ChatOpenAI(model_name=\"gpt-4o-mini\")\n",
    "\n",
    "\n",
    "# Function to format retrieved documents.\n",
    "def format_docs(docs):\n",
    "    return \"\\n\".join([doc.page_content for doc in docs])\n",
    "\n",
    "\n",
    "# Construct the retrieval chain.\n",
    "retrieval_chain = (\n",
    "    {\"context\": retriever | format_docs, \"question\": RunnablePassthrough()}\n",
    "    | prompt\n",
    "    | model\n",
    "    | StrOutputParser()\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Cats like boxes.'"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Query retrieval chain\n",
    "retrieval_chain.invoke(\"What kind of objects do cats like?\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Dogs like to befriend cats.'"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "retrieval_chain.invoke(\"What do dogs like?\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Using Ollama\n",
    "\n",
    "- Download the application from the [Ollama official website](https://ollama.com/)\n",
    "- For comprehensive Ollama documentation, visit the [GitHub tutorial](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/04-Model/10-Ollama.ipynb)\n",
    "- Implementation utilizes the ```llama3.2``` 1b model for response generation and ```mxbai-embed-large``` for embedding operations"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Ollama Installation Guide on Colab**\n",
    "\n",
    "Google Colab requires the ```colab-xterm``` extension for terminal functionality. Follow these steps to install Ollama:\n",
    "\n",
    "1. **Install and Initialize ```colab-xterm```**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install colab-xterm\n",
    "%load_ext colabxterm"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. **Launch Terminal**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%xterm"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3. **Install Ollama**\n",
    "\n",
    "    Execute the following command in the terminal:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "curl -fsSL https://ollama.com/install.sh | sh"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "4. **Installation Verification**\n",
    "\n",
    "    Verify installation by running:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ollama"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Successful installation displays the \"Available Commands\" menu."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "5. **Download and Prepare the Embedding Model for Ollama**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!ollama pull mxbai-embed-large"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_community.vectorstores import FAISS\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_core.runnables import RunnablePassthrough\n",
    "from langchain_ollama import OllamaEmbeddings\n",
    "\n",
    "# Configure embeddings\n",
    "ollama_embeddings = OllamaEmbeddings(model=\"mxbai-embed-large\")\n",
    "\n",
    "# Initialize FAISS vector store with text data\n",
    "vectorstore = FAISS.from_texts(\n",
    "    [\n",
    "        \"Cats are geniuses at claiming boxes as their own.\",\n",
    "        \"Dogs have successfully trained humans to take them for walks.\",\n",
    "        \"Cats aren't fond of water, but the water in a human's cup is an exception.\",\n",
    "        \"Dogs follow cats around, eager to befriend them.\",\n",
    "        \"Cats consider laser pointers their arch-nemesis.\",\n",
    "    ],\n",
    "    embedding=ollama_embeddings(),\n",
    ")\n",
    "# Convert vector store to retriever\n",
    "retriever = vectorstore.as_retriever()\n",
    "\n",
    "# Define prompt template\n",
    "template = \"\"\"Answer the question based only on the following context:\n",
    "{context}\n",
    "\n",
    "Question: {question}\n",
    "\"\"\"\n",
    "\n",
    "# Initialize chat prompt from template\n",
    "prompt = ChatPromptTemplate.from_template(template)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "6. **Download and Prepare the Model for Answer Generation**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!ollama pull llama3.2:1b"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_ollama import ChatOllama\n",
    "\n",
    "# Initialize Ollama chat model\n",
    "ollama_model = ChatOllama(model=\"llama3.2:1b\")\n",
    "\n",
    "\n",
    "# Format retrieved documents\n",
    "def format_docs(docs):\n",
    "    return \"\\n\".join([doc.page_content for doc in docs])\n",
    "\n",
    "\n",
    "# Build retrieval chain\n",
    "retrieval_chain = (\n",
    "    {\"context\": retriever | format_docs, \"question\": RunnablePassthrough()}\n",
    "    | prompt\n",
    "    | ollama_model  # Use Ollama model for inference\n",
    "    | StrOutputParser()\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Based on this context, it seems that cats tend to enjoy and claim boxes as their own.'"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Query retrieval chain\n",
    "retrieval_chain.invoke(\"What kind of objects do cats like?\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Based on the context, it seems that dogs enjoy being around cats and having them follow them. Additionally, dogs have successfully trained humans to take them for walks.'"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Query retrieval chain\n",
    "retrieval_chain.invoke(\"What do dogs like?\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-opentutorial-KA-VgDpL-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
